package drep;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.Socket;

/**
 * Universidad Simón Bolívar
 * Curso de Redes I - Taller
 * Profesor: Ricardo Gonzalez
 * Trimestre Enero-Marzo 2011
 *
 * Proyecto 2 RD
 *
 * @author Grupo 9
 * @author Richard Simoes Ferreira
 * @author Ricardo Tocco
 *
 * @version 1.0
 */
public class ServiceDrep extends Thread {

/**
 * Implementación de un servicio que se ofrece a un cliente del servidor drep
 */
    private Socket cliente = null;
    private ControlDrep control;
    private String usernameCliente = null;

    /**
     * Constructor de ServiceDrep
     * @param s Socket del cliente al cual se le ofrece el servicio
     * @param c Estructura de control drep que contine los diferentes servicios
     */
    public ServiceDrep (Socket s, ControlDrep c){
        //super("ServiceDrep");
        this.cliente = s;
        this.control = c;
    }

    /**
     * Código que ejecuta el hilo encargado de implementar el servicio
     */
    @Override
    public void run(){
        ErrorHandler errorH = new ErrorHandler(control);
        boolean fin = false;
        while (!fin){
            try {
                //Escuchar Peticion (Protocolo)
                //Responder Peticion
                //Termina

                String solicitud;
                String header = Getlinea();
                String argumentos[];
                //Se segmenta el mensaje de acuerdo con el separador del
                //protocolo
                argumentos = header.split("\\##%");
                solicitud = argumentos[0];

                if (solicitud.equals("tuser")){
                //Se solicita mostrar todos los nombres de usuarios dcli activados
                //Solicitamos "tuser" a Dmon y esperamos respuesta
                //Segun el protocolo le respondemos con la lista de usuarios dcli
                // conectados
                    if (control.tuser().equals("excepcion")){
                        sendLinea(control.tuser());
                        fin = true;
                        errorH.terminarDrep(cliente);
                    }
                    sendLinea(control.tuser());
                } else if (solicitud.equals("nrep")){
                    //Se solicita mostrar el numero de Drep activados
                    //Solicitamos "nrep" a Dmon y esperamos respuesta
                    //Segun el protocolo le respondemos con la lista de nombres
                    // y tamaños de los archivos en rdirectorio
                    //Se envia información sobre la conexion de un dcli al Dmon
                    //Segun el protocolo le respondemos con ACCEPT
                    String response;
                    response = control.nrep();
                    if (response == null) {
                        sendLinea("error");
                    } else if (response.equals("excepcion")){
                        sendLinea("excepcion");
                        fin = true;
                        errorH.terminarDrep(cliente);

                    } else {
                        sendLinea(response);
                    }
                } else if (solicitud.equals("rdir")){
                    //Solicitan los nombres y tamaños de los archivos en drep
                    //Segun el protocolo le respondemos con la lista de nombres
                    // y tamaños de los archivos en rdirectorio
                    rdir();
                } else if (solicitud.equals("s")){
                //Se solicita subir el archivo de nombre tokens.nextToken()
                //al rdirectorio
                //Se envia información de una nueva subida de archivo al Dmon
                //Segun el protocolo le respondemos con CHECK
                    sube(argumentos[1], argumentos[2]);
                } else if (solicitud.equals("b")){
                    //Se solicita bajar el archivo de nombre tokens.nextToken() 
                    //del rdirectorio
                    //Se envia información de una nueva bajada de archivo al Dmon
                    //Segun el protocolo le respondemos con CHECK
                    baja(argumentos[1]);
                } else if (solicitud.equals("newDcli")){
                    //Se envia información sobre la conexion de un dcli al Dmon
                    //Segun el protocolo le respondemos con ACCEPT
                    int status = -1;

                    if (argumentos.length<2){
                        sendLinea("error parametros");
                    } else {
                        status = control.incrementarActivosDcli(argumentos[1]);
                    }
                    
                    if (status < 0) {
                        if (status == -1){
                            sendLinea("error");
                        } else {
                            sendLinea("excepcion");
                            fin = true;
                            errorH.terminarDrep(cliente);
                        }
                        
                    } else {
                        this.usernameCliente = argumentos[1];
                        sendLinea("accept");
                    }
                } else if (solicitud.equals("disconnectDcli")){
                    //Se envia información sobre la desconexion de un dcli 
                    //llamado tokens.nextToken() al Dmon
                    //Segun el protocolo le respondemos con CHECK
                    int status = -1;

                    if (argumentos.length<2){
                        sendLinea("error parametros");
                    } else {
                        status = control.decrementarActivosDcli(argumentos[1]);
                    }

                    if (status < 0) {
                        if (status == -1){
                            sendLinea("error");
                        } else {
                            sendLinea("excepcion");
                            fin = true;
                            errorH.terminarDrep(cliente);
                        }
                    } else {
                        sendLinea("accept");
                        fin = true;
                        this.cliente.close();
                    }
                    
                } else {
                   //No se entendio la solicitud
                }

            } catch (IOException ex) {
                errorH.errorBufferGetLineaDcli(control, cliente);
            }
        }//Fin While
        
    }
    
    /**
     * Obtiene una linea del flujo de comunicación enviado desde el servidor
     * drep
     * @return  Linea capturada a través del socket de comunicación
     */
    public String Getlinea() {

        String buf;
        ErrorHandler errorH = new ErrorHandler();

        try {

            BufferedReader in = new BufferedReader(new InputStreamReader(this.cliente.getInputStream()));

            if((buf=in.readLine()) != null){
                return buf;
            } else {
                //No se pudo leer nada
                errorH.errorBufferGetLineaDcli(this.control,this.cliente);
                return "disconnectDcli##%"+this.usernameCliente;
            }

        } catch (IOException e){
            errorH.errorGetLineaDcli(this.control,this.cliente);
            return null;
        }
    }

    /**
     * Envía un mensaje a través del socket de comunicación.
     * @param msg   Mensaje que se desea transmitir.
     * @return  0 si no hubo problemas y otro valor sino.
     */
    public int sendLinea(String msg){

        PrintWriter out = null;
        ErrorHandler errorH = new ErrorHandler();

        try {
            out = new PrintWriter(this.cliente.getOutputStream(),true);
        } catch (IOException e){
            errorH.errorSendLineaDcli(this.control,e);
            return -1;
        }
        //Enviamos el mensaje
        out.println(msg);
        return 0;
    }


     /**
     * Obtiene un byte del flujo de comunicación enviado desde el servidor
     * drep
     * @return  Byte capturado a través del socket de comunicación
     * @throws IOException  Cuando ocurre un error con la comunicación
     */
    public Byte getByte() throws IOException{
        ErrorHandler errorH = new ErrorHandler();
        Byte buf;

        try {

            DataInputStream entrada = new DataInputStream(cliente.getInputStream());

            if((buf=entrada.readByte()) != null){
                //in.close();
                return buf;
            } else {
                entrada.close();
                errorH.errorBufferGetLineaDcli(this.control,this.cliente);
                return null;
            }

        } catch (IOException e){
            errorH.errorGetLineaDcli(this.control,this.cliente);
            return null;
        }
    }

    /**
     * Envía un byte a través del socket de comunicación.
     * @param msg   Byte que se desea transmitir.
     * @return  0 si no hubo problemas y otro valor sino.
     */
    public int sendByte(Byte msg){

        ErrorHandler errorH = new ErrorHandler();
        try {

            DataOutputStream cli = new DataOutputStream(this.cliente.getOutputStream());
            cli.writeByte(msg);

        } catch (IOException e){
            errorH.errorSendLineaDcli(this.control,e);
            return 0;
        }
        return 0;
    }

    /**
     * Envía un long a través del socket de comunicación.
     * @param msg   Long que se desea transmitir.
     * @return  0 si no hubo problemas y otro valor sino.
     */
    public int sendLong(Long msg){
        ErrorHandler errorH = new ErrorHandler();
        try {

            DataOutputStream cli = new DataOutputStream(cliente.getOutputStream());
            cli.writeLong(msg);

        } catch (IOException e){
            errorH.errorSendLineaDcli(this.control,e);
            return 0;
        }
        return 0;
    }

    /**
     * Obtiene un byte del flujo de comunicación enviado desde el servidor
     * drep
     * @return  Byte capturado a través del socket de comunicación
     * @throws IOException  Cuando ocurre un error con la comunicación
     */
    public Long getLong() throws IOException{
        ErrorHandler errorH = new ErrorHandler();
        Long buf;

        try {

            DataInputStream entrada = new DataInputStream(cliente.getInputStream());

            if((buf=entrada.readLong()) != null){
                //in.close();
                return buf;
            } else {
                errorH.errorBufferGetLineaDcli(this.control,this.cliente);
                return null;
            }

        } catch (IOException e){
            errorH.errorGetLineaDcli(this.control,this.cliente);
            return null;
        }
    }

    /**
     * Maneja la operación de subida de archivos desde un dcli.
     *
     * @param nombreArchivo     el nombre archivo que se subirá al drep.
     * @param longitudArchivo   el tamaño del archivo que será almacenado en el
     *                          servidor
     */
     private void sube(String nombreArchivo, String longitudArchivo) {

        RandomAccessFile archivo = null;
        ErrorHandler errorH = new ErrorHandler();
        File arch = null;

        try{
            arch = new File(control.getRdir()+nombreArchivo);

            if (arch.exists())
                arch.delete();
            archivo = new RandomAccessFile(arch, "rw");

            sendLinea("fino");
            Long tamArchivo = new Long(longitudArchivo);
            int numBytes = 0;
            while(numBytes < tamArchivo) {
                byte dato = getByte();
                archivo.writeByte(dato);
                numBytes++;
            }
            
            if(longitudArchivo.equals(String.valueOf(archivo.length()))) {
                sendLinea("1");
                //System.out.println("La transferencia del archivo terminó " +
                //        "satisfactoriamente...");
                control.incrementarSubidas();
            } else{
                sendLinea("0");
                errorH.errorTransferenciaIncompletaSube();
            }
        }  catch(FileNotFoundException h) {
            sendLinea("error");
            errorH.errorCreacionArchivoSube(h, nombreArchivo);
        } catch(IOException e) {
            errorH.errorTransferenciaSube(e);
        } finally {
            try {
                if (archivo != null) {
                    archivo.close();
                }
            } catch (IOException ex) {
                errorH.errorTransferenciaIncompletaSube();
            }
        }
    }

     /**
     * Maneja la operación de descarga de archivos a un dcli.
     *
     * @param nombreArchivo     el nombre archivo que se descargará a un dcli
     */
     private void baja(String nombreArchivo) {
         RandomAccessFile archivo = null;
         ErrorHandler errorH = new ErrorHandler();
        try{
            archivo = new RandomAccessFile(control.getRdir()+nombreArchivo,"r");
            //System.out.println("Inicia la transferencia del archivo...");
            sendLong(archivo.length());
            String st = Getlinea();
            if (st.equals("error")) {
                errorH.errorTransferenciaIncompletaBaja();
                return;
            }

            while(archivo.getFilePointer()<archivo.length()) {
                byte dato = archivo.readByte();
                sendByte(dato);
            }
            
            String s = Getlinea();
            if (s.equals("1")) {
                //System.out.println("La transferencia del archivo " +
                //        "terminó satisfactoriamente.");
                control.incrementarBajadas();
            }
            else if (s.equals("0")){
                errorH.errorTransferenciaIncompletaBaja();
            }
        
        } catch(FileNotFoundException h) {
            errorH.errorArchivoNoExisteBaja(nombreArchivo);
            sendLong(-1l);
        } catch(IOException e) {
            errorH.errorTransferenciaBaja(e);
        } finally {
            try {
                if (archivo != null) {
                    archivo.close();
                }
            } catch (IOException ex) {
                errorH.errorTransferenciaIncompletaBaja();
            }
        }

    }

    /**
     * Envía al dcli solicitante los nombres y tamaños de cada uno de los
     * archivos locales al programa drep, es decir, de los archivos que estén
     * contenidos en el directorio rdir.
     */
    private void rdir() {
        File  dir = new File(control.getRdir());
        File [] files = dir.listFiles();
        if (files.length != 0){
            String buf = (files[0].getName()+"\t"+files[0].length());
            
            for (int i=1;i<files.length;i++){
                buf += "##%"+files[i].getName()+"\t"+files[i].length();
            }
            sendLinea(buf);
        } else sendLinea("vacio");
    }
    
}
